from utils import StatusUpdateTool, Utils, Log
from genetic.population import Population
from genetic.evaluate import FitnessEvaluate
from genetic.crossover_and_mutation import CrossoverAndMutation
from genetic.selection_operator import Selection
from genetic.crossover_and_mutation import Crossover
import numpy as np
import copy

class EvolveCNN(object):
    def __init__(self, params):
        self.params = params
        self.pops = None
        self.nsflag = np.zeros((params['pop_size'],params['mutation_amount']))#自适应变异部分，3代表变异的数量，如有需要建议改变
        self.nfflag = np.zeros((params['pop_size'],params['mutation_amount']))#nsflag和nfflag记录一代里面的信息
        self.sucess_gen= np.zeros((params['max_gen'],params['mutation_amount']))#success_gen记录所有代的成功数据
        self.fail_gen = np.zeros((params['max_gen'], params['mutation_amount']))
        self.Mutation_pro=StatusUpdateTool.get_mutation_probs_for_each()#获取每一个变异的初始概率

    def initialize_population(self):
        StatusUpdateTool.begin_evolution()
        pops = Population(params, 0)
        pops.initialize()
        self.pops = pops
        Utils.save_population_at_begin(str(pops), 0)

    def fitness_evaluate(self):
        fitness = FitnessEvaluate(self.pops.individuals, Log)
        fitness.generate_to_python_file()
        fitness.evaluate()

    def rejust_ns_nf(self,curr_gen):#记录一代里面所有个体更新成功或者失败的信

        for i in range(0,self.params['pop_size']):
            if self.pops.individuals[i].Mutation_type!=3:  #type=3表示没有发生变异
                if self.pops.individuals[i].acc >= self.parent_pops.individuals[i].acc: #父代与子代
                    self.nsflag[i,self.pops.individuals[i].Mutation_type] = 1
                else:
                    self.nfflag[i, self.pops.individuals[i].Mutation_type] = 1#self.nsflag #nsflag和nfflag
        self.sucess_gen[curr_gen - 1, :] = self.nsflag.sum(axis=0)#将当前代的成功次数按列相加放入sucess_gen里面
        self.fail_gen[curr_gen - 1, :] = self.nfflag.sum(axis=0)
        self.nsflag = np.zeros((params['pop_size'], params['mutation_amount']))#清空一代里面的nsflag与nfflag
        self.nfflag = np.zeros((params['pop_size'], params['mutation_amount']))

    def rejust_pro(self):#记录一个更新循环后更新的自适应变异策略

        temp_success = self.sucess_gen.sum(axis=0)#将success_gen和fail_gen按列相加
        temp_fail = self.fail_gen.sum(axis=0)

        for i in range(0,temp_success.size):#防止一个策略从来没有选择导致的概率变为0
            if temp_success[i]==0 and temp_fail[i]==0:
                temp_success[i]=1

        temp_sum = temp_success + temp_fail   #将成功的记录矩阵temp_success按行相加
        temp_pro = temp_success / temp_sum    #将每个成功的次数除以总数得到一个一维的概率矩阵

        #概率归一化
        sum=0
        new_pro=np.zeros(temp_pro.size)

        for i in range(0,temp_pro.size):#次数求和
            sum=sum+temp_pro[i]

        for i in range(0,temp_pro.size):#将本身除以总和的概率得到归一化后的概率
            new_pro[i]=temp_pro[i]/sum

        self.Mutation_pro=new_pro   #更新完一个更新代数后的概率
        #一个更新轮回后清空success_gen和fail_gen
        self.sucess_gen = np.zeros((params['max_gen'], params['mutation_amount']))  # success_gen和fail清空
        self.fail_gen = np.zeros((params['max_gen'], params['mutation_amount']))







    def crossover_and_mutation(self):
        cm = CrossoverAndMutation(self.params['genetic_prob'][0], self.params['genetic_prob'][1], Log,
                                  self.pops.individuals, self.Mutation_pro,_params={'gen_no': self.pops.gen_no})#从主函数调用crossover的函数
        offspring = cm.process()
        self.parent_pops = copy.deepcopy(self.pops)
        self.pops.individuals = copy.deepcopy(offspring)

    def environment_selection(self):
        v_list = []
        indi_list = []
        for indi in self.pops.individuals:  #将父代与子代都保存在indi_list中
            indi_list.append(indi)
            v_list.append(indi.acc)#v_list存放精度
        for indi in self.parent_pops.individuals:
            indi_list.append(indi)
            v_list.append(indi.acc)

        _str = []
        for _, indi in enumerate(self.pops.individuals):
            _t_str = 'Indi-%s-%.5f-%s'%(indi.id, indi.acc, indi.uuid()[0])
            _str.append(_t_str)   #将子代中每个个体的编号，精度，哈希编码放入_str中   indi开头的是子代
        for _, indi in enumerate(self.parent_pops.individuals):
            _t_str = 'Pare-%s-%.5f-%s'%(indi.id, indi.acc, indi.uuid()[0])
            _str.append(_t_str)  #将父代中每个个体的编号，精度，哈希编码放入_str中     Pare开头的是父代


        #add log
        # find the largest one's index
        # max_index = np.argmax(v_list)  #返回；精度最大的那个索引值
        # selection = Selection()  #创建一个轮盘赌算法类的对象
        # selected_index_list = selection.RouletteSelection(v_list, k=self.params['pop_size'])#传入精度列表和需要筛选的种群数量
        # if max_index not in selected_index_list:  #如果父代子代种群中最好的个体不在，那么把最差的个体代替为最好的个体
        #     first_selectd_v_list = [v_list[i] for i in selected_index_list]
        #     min_idx = np.argmin(first_selectd_v_list)
        #     selected_index_list[min_idx] = max_index
        #
        # next_individuals = [indi_list[i] for i in selected_index_list]


        # v_list精度的数量
        # indi_list 表示父代与子代合并的总种群 既最终选好的种群
        # max_index存放最大精度的索引值
        # selected_index_list 选择策略选择的精度索引值，个数是个变量长度
        # next_individuals  下一代种群
        # self.params['pop_size'] 种群的数量
        #二元竞赛从cross_and_mutation里面的_choose_two_diff_parents方法找
        #next_individuals 尺寸应该是N
        #修改版
        top_k = np.random.randint(1,self.params['pop_size']/2) #随机选择pop_size/2个个体适应度值最大的拿出来
        arr = np.array(v_list)         #转化为数组
        top_k_idx = arr.argsort()[::-1][0:top_k]  # (pop_size/2)个最大的索引值获取
        #半完全二元竞赛，先随机生成一个N/2的数字top_k，把总种群中最大的top_k个数字放入k列表中，再将剩下的进行二元竞赛
        k=[]
        for i in range(0, self.params['pop_size']*2):
            if i not in top_k_idx:  #k中存放去掉top_k_idx个最大值的剩余个体
                k.append(indi_list[i])


        #idx1和idx2分别表示从剩下的种群中选择的两个个体
        _count=len(k)
        next_individuals = []
        for i in range(0,self.params['pop_size']-top_k):   #二元竞赛从去掉top_k个个体的种群中选择个体进行二元竞赛
            idx1 = int(np.floor(np.random.random() * _count))#随机生成个体一和二的位置
            idx2 = int(np.floor(np.random.random() * _count))
            while idx2 == idx1:
                idx2 = int(np.floor(np.random.random() * _count))

            if k[idx1].acc > k[idx2].acc:
                next_individuals.append(k[idx1])
            else:
                next_individuals.append(k[idx2])

        for i in range(top_k):   #将开始提取出来的最大的top_k个个体放在next_individuals的最后
            next_individuals.append(indi_list[top_k_idx[i]])#这样最终的next_individuals保存了N个个体
        # selected_index_list=top_k_idx

        """Here, the population information should be updated, such as the gene no and then to the individual id"""
        next_gen_pops = Population(self.pops.params, self.pops.gen_no+1)
        next_gen_pops.create_from_offspring(next_individuals)
        self.pops = next_gen_pops
        for _, indi in enumerate(self.pops.individuals):
            _t_str = 'new -%s-%.5f-%s'%(indi.id, indi.acc, indi.uuid()[0])
            _str.append(_t_str)
        _file = './populations/ENVI_%2d.txt'%(self.pops.gen_no)
        Utils.write_to_file('\n'.join(_str), _file)

        Utils.save_population_at_begin(str(self.pops), self.pops.gen_no)

    def do_work(self, max_gen):
        Log.info('*'*25)
        # the step 1
        if StatusUpdateTool.is_evolution_running():  #判断global文件里面的演化状态是否为1
            Log.info('Initialize from existing population data')
            gen_no = Utils.get_newest_file_based_on_prefix('begin')
            if gen_no is not None:
                Log.info('Initialize from %d-th generation'%(gen_no))
                pops = Utils.load_population('begin', gen_no)
                self.pops = pops
            else:
                raise ValueError('The running flag is set to be running, but there is no generated population stored')
        else:
            gen_no = 0
            Log.info('Initialize...')
            self.initialize_population()
        Log.info('EVOLVE[%d-gen]-Begin to evaluate the fitness'%(gen_no))
        self.fitness_evaluate()#评估当前代的所有个体
        Log.info('EVOLVE[%d-gen]-Finish the evaluation'%(gen_no))
        gen_no += 1
        for curr_gen in range(gen_no, max_gen):
            self.params['gen_no'] = curr_gen
            #step3
            Log.info('EVOLVE[%d-gen]-Begin to crossover and mutation'%(curr_gen))
            self.crossover_and_mutation()
            Log.info('EVOLVE[%d-gen]-Finish crossover and mutation'%(curr_gen))

            Log.info('EVOLVE[%d-gen]-Begin to evaluate the fitness'%(curr_gen))
            self.fitness_evaluate()
            Log.info('EVOLVE[%d-gen]-Finish the evaluation'%(curr_gen))
            self.rejust_ns_nf(curr_gen) #适应度评估完成后比较父代与子代的好坏 自适应机制调整nsflag与nfflag
            Log.info('EVOLVE[%d-gen]-Finish the nsflag and nfflag' % (curr_gen))
            if curr_gen % 5==0:   #由于总代数的限制，这里设置成五代就更新一次概率
                self.rejust_pro()
            self.environment_selection()
            Log.info('EVOLVE[%d-gen]-Finish the environment selection'%(curr_gen))

        StatusUpdateTool.end_evolution()

if __name__ == '__main__':
    params = StatusUpdateTool.get_init_params()   #获取global文件里面的基本数据
    evoCNN = EvolveCNN(params)
    evoCNN.do_work(params['max_gen'])



